package com.liuqianfei.markme.rules;

import java.util.ArrayList;
import java.util.List;

import com.liuqianfei.markme.MarkMeParser;
import com.liuqianfei.markme.common.Utils;
import com.liuqianfei.markme.core.Token;
import com.liuqianfei.util.StringUtil;

public class StateInline extends AbstractState
{
    public String src;
    public MarkMeParser md;
    public Environment env;
    public List<Token> tokens = new ArrayList<>();
    
    public int pos;
    public int posMax;
    public int level;
    public String pending;
    public int pendingLevel;
    
    // Stores { start: end } pairs. Useful for backtrack
    // optimization of pairs parse (emphasis, strikes).
    public List<Integer> cache;
    
    // Emphasis-like delimiters
    public List<Delimiter> delimiters;
    
    public StateInline(String src, MarkMeParser md, Environment env, List<Token> tokens)
    {
        this.src = src;
        this.md = md;
        this.env = env;
        this.tokens = tokens;
        
        pos = 0;
        posMax = this.src.length();
        level = 0;
        pending = "";
        pendingLevel = 0;
        cache = new ArrayList<>();
        delimiters = new ArrayList<>();
    }
    
    /**
     * Flush pending text
     */
    public Token pushPending()
    {
        Token token = new Token("text", "", 0);
        token.content = this.pending;
        token.level = this.pendingLevel;
        this.tokens.add(token);
        this.pending = "";
        return token;
    }
    
    /**
     * Push new token to "stream".
     * If pending text exists - flush it as text token
     */
    @Override
    public Token push(String type, String tag, int nesting)
    {
        if (StringUtil.isNotEmpty(this.pending))
            this.pushPending();

        Token token = new Token(type, tag, nesting);

        if (nesting < 0)
            this.level--;

        token.level = this.level;

        if (nesting > 0)
            this.level++;

        this.pendingLevel = this.level;
        this.tokens.add(token);

        return token;
    }
    
    /**
     * Scan a sequence of emphasis-like markers, and determine whether
     * it can start an emphasis sequence or end an emphasis sequence.
     * 
     * @param start position to scan from (it should point at a valid marker);
     * @param canSplitWord determine if these markers can be found inside a word
     */
    @Override
    public EmphasisLikeScanResult scanDelims(int start, boolean canSplitWord)
    {
        int pos = start;
        char lastChar, nextChar, marker = this.src.charAt(start);
        int count, max = this.posMax;
        boolean can_open, can_close,
            isLastWhiteSpace, isLastPunctChar,
            isNextWhiteSpace, isNextPunctChar,
            left_flanking = true,
            right_flanking = true;

        // treat beginning of the line as a whitespace
        lastChar = start > 0 ? this.src.charAt(start - 1) : 0x20;

        while (pos < max && (this.src.charAt(pos) == marker))
            pos++;

        count = pos - start;

        // treat end of the line as a whitespace
        nextChar = pos < max ? this.src.charAt(pos) : 0x20;

        isLastPunctChar = Utils.isMdAsciiPunct(lastChar) || Utils.isPunctChar(String.valueOf(lastChar));
        isNextPunctChar = Utils.isMdAsciiPunct(nextChar) || Utils.isPunctChar(String.valueOf(nextChar));

        isLastWhiteSpace = Utils.isWhiteSpace(lastChar);
        isNextWhiteSpace = Utils.isWhiteSpace(nextChar);

        if (isNextWhiteSpace)
            left_flanking = false;
        else if (isNextPunctChar)
        {
            if (!(isLastWhiteSpace || isLastPunctChar))
                left_flanking = false;
        }

        if (isLastWhiteSpace)
            right_flanking = false;
        else if (isLastPunctChar)
        {
            if (!(isNextWhiteSpace || isNextPunctChar))
            {
                right_flanking = false;
            }
        }

        if (!canSplitWord)
        {
            can_open  = left_flanking  && (!right_flanking || isLastPunctChar);
            can_close = right_flanking && (!left_flanking  || isNextPunctChar);
        }
        else
        {
            can_open  = left_flanking;
            can_close = right_flanking;
        }
        
        EmphasisLikeScanResult emphasisLikeScanResult = new EmphasisLikeScanResult();
        emphasisLikeScanResult.can_open = can_open;
        emphasisLikeScanResult.can_close = can_close;
        emphasisLikeScanResult.length = count;

        return emphasisLikeScanResult;
    }

    @Override
    public Environment getEnv()
    {
        return env;
    }

    @Override
    public void setEnv(Environment env)
    {
        this.env = env;
    }

    @Override
    public String getSrc()
    {
        return src;
    }

    @Override
    public MarkMeParser getMd()
    {
        return md;
    }

    @Override
    public List<Token> getTokens()
    {
        return tokens;
    }

    @Override
    public Token getToken(int index)
    {
        return tokens.get(index);
    }

    @Override
    public void setTokens(List<Token> tokens)
    {
        this.tokens = tokens;
    }

    @Override
    public void setToken(int index, Token token)
    {
        tokens.set(index, token);
    }

    @Override
    public int getPos()
    {
        return pos;
    }

    @Override
    public void setPos(int pos)
    {
        this.pos = pos;
    }

    @Override
    public int getPosMax()
    {
        return posMax;
    }

    @Override
    public void setPosMax(int posMax)
    {
        this.posMax = posMax;
    }

    @Override
    public int getLevel()
    {
        return level;
    }

    @Override
    public void setLevel(int level)
    {
        this.level = level;
    }

    @Override
    public String getPending()
    {
        return pending;
    }

    @Override
    public void setPending(String pending)
    {
        this.pending = pending;
    }

    @Override
    public List<Delimiter> getDelimiters()
    {
        return delimiters;
    }

    @Override
    public void setDelimiters(List<Delimiter> delimiters)
    {
        this.delimiters = delimiters;
    }
}
